//
//  AlphaBitmap.cpp
//  VisionGame
//
//  Created by aaronwang on 13-1-8.
//
//

#include "QAlphaBitmap.h"
#include <QtCore/QFile>
#include <QtCore/QDataStream>


struct BMP_FILEHDR
{                     // BMP file header
    char   bfType[2];                    // "BM"
    qint32  bfSize;                      // size of file
    qint16  bfReserved1;
    qint16  bfReserved2;
    qint32  bfOffBits;                   // pointer to the pixmap bits
};

struct BMP_INFOHDR
{                     // BMP information header
    qint32  biSize;                      // size of this struct
    qint32  biWidth;                     // pixmap width
    qint32  biHeight;                    // pixmap height
    qint16  biPlanes;                    // should be 1
    qint16  biBitCount;                  // number of bits per pixel
    qint32  biCompression;               // compression method
    qint32  biSizeImage;                 // size of image
    qint32  biXPelsPerMeter;             // horizontal resolution
    qint32  biYPelsPerMeter;             // vertical resolution
    qint32  biClrUsed;                   // number of colors used
    qint32  biClrImportant;              // number of important colors
};

const int BMP_FILEHDR_SIZE = 14;                // size of BMP_FILEHDR data

static QDataStream &operator>>(QDataStream &s, BMP_FILEHDR &bf)
{                                                // read file header
    s.readRawData(bf.bfType, 2);
    s >> bf.bfSize >> bf.bfReserved1 >> bf.bfReserved2 >> bf.bfOffBits;
    return s;
}

static QDataStream &operator<<(QDataStream &s, const BMP_FILEHDR &bf)
{                                                // write file header
    s.writeRawData(bf.bfType, 2);
    s << bf.bfSize << bf.bfReserved1 << bf.bfReserved2 << bf.bfOffBits;
    return s;
}


const int BMP_OLD  = 12;                        // old Windows/OS2 BMP size
const int BMP_WIN  = 40;                        // new Windows BMP size
const int BMP_OS2  = 64;                        // new OS/2 BMP size

const int BMP_RGB  = 0;                                // no compression
const int BMP_RLE8 = 1;                                // run-length encoded, 8 bits
const int BMP_RLE4 = 2;                                // run-length encoded, 4 bits
const int BMP_BITFIELDS = 3;                        // RGB values encoded in data as bit-fields

static QDataStream &operator>>(QDataStream &s, BMP_INFOHDR &bi)
{
    s >> bi.biSize;
    if (bi.biSize == BMP_WIN || bi.biSize == BMP_OS2) {
        s >> bi.biWidth >> bi.biHeight >> bi.biPlanes >> bi.biBitCount;
        s >> bi.biCompression >> bi.biSizeImage;
        s >> bi.biXPelsPerMeter >> bi.biYPelsPerMeter;
        s >> bi.biClrUsed >> bi.biClrImportant;
    }
    else {                                        // probably old Windows format
        qint16 w, h;
        s >> w >> h >> bi.biPlanes >> bi.biBitCount;
        bi.biWidth  = w;
        bi.biHeight = h;
        bi.biCompression = BMP_RGB;                // no compression
        bi.biSizeImage = 0;
        bi.biXPelsPerMeter = bi.biYPelsPerMeter = 0;
        bi.biClrUsed = bi.biClrImportant = 0;
    }
    return s;
}

static QDataStream &operator<<(QDataStream &s, const BMP_INFOHDR &bi)
{
    s << bi.biSize;
    s << bi.biWidth << bi.biHeight;
    s << bi.biPlanes;
    s << bi.biBitCount;
    s << bi.biCompression;
    s << bi.biSizeImage;
    s << bi.biXPelsPerMeter << bi.biYPelsPerMeter;
    s << bi.biClrUsed << bi.biClrImportant;
    return s;
}

static bool read_dib_fileheader(QDataStream &s, BMP_FILEHDR &bf)
{
    // read BMP file header
    s >> bf;
    if (s.status() != QDataStream::Ok)
        return false;
    
    // check header
    if (qstrncmp(bf.bfType,"BM",2) != 0)
        return false;
    
    return true;
}

static bool read_dib_infoheader(QDataStream &s, BMP_INFOHDR &bi)
{
    s >> bi;                                        // read BMP info header
    if (s.status() != QDataStream::Ok)
        return false;
    
    int nbits = bi.biBitCount;
    int comp = bi.biCompression;
    if (!(nbits == 1 || nbits == 4 || nbits == 8 || nbits == 16 || nbits == 24 || nbits == 32) ||
        bi.biPlanes != 1 || comp > BMP_BITFIELDS)
        return false;                                        // weird BMP image
    if (!(comp == BMP_RGB || (nbits == 4 && comp == BMP_RLE4) ||
          (nbits == 8 && comp == BMP_RLE8) || ((nbits == 16 || nbits == 32) && comp == BMP_BITFIELDS)))
        return false;                                // weird compression type
    
    return true;
}

QAlphaBitmap::QAlphaBitmap(const QString &filename)
{
    QFile file(filename);
    file.open(QIODevice::ReadOnly);
    QDataStream stream(&file);
    
    stream.setByteOrder(QDataStream::LittleEndian);
    
    BMP_FILEHDR bf;
    read_dib_fileheader(stream, bf);
    BMP_INFOHDR bi;
    read_dib_infoheader(stream, bi);
    
    // Flip data
    bool shouldFlip = true;
    
    if (bi.biHeight < 0)
    {
        bi.biHeight = -bi.biHeight;
        shouldFlip = false;
    }
    
    mData = new quint32[bi.biWidth * bi.biHeight];

#if 1
    quint32 *data = new quint32[bi.biWidth * bi.biHeight];
    stream.readRawData((char*)data, bi.biWidth*bi.biHeight*sizeof(quint32));
    
    if (shouldFlip)
    {
        quint32 *src = data + (bi.biHeight - 1) * bi.biWidth;
        quint32 *dst = mData;
        
        for (qint32 i = bi.biHeight - 1; i >= 0; --i)
        {
            memcpy(dst, src, bi.biWidth * sizeof(quint32));
            src -= bi.biWidth;
            dst += bi.biWidth;
        }
    }
    else
    {
        memcpy(mData, data, bi.biWidth * bi.biHeight * sizeof(quint32));
    }
    
    delete []data;
#else
	stream.readRawData((char*)mData, bi.biWidth*bi.biHeight*sizeof(quint32));
#endif
    
    mImage = new QImage((uchar*)mData, bi.biWidth, bi.biHeight, bi.biWidth*sizeof(quint32), QImage::Format_ARGB32);

    file.close();
}

QAlphaBitmap::QAlphaBitmap(int width, int height)
{
    mData = new quint32[width * height];
    
    memset(mData, 0, sizeof(quint32)*width*height);
    
    mImage = new QImage((uchar*)mData, width, height, width*sizeof(quint32), QImage::Format_ARGB32);
}

QAlphaBitmap::~QAlphaBitmap()
{
    delete mImage;
    delete []mData;
}

void QAlphaBitmap::fill(QAlphaBitmap *srcImage, const QPoint &pos)
{
    int dstW = mImage->width();
    int dstH = mImage->height();
    
    int srcW = srcImage->width();
    int srcH = srcImage->height();
    
    if (srcW > dstW || srcH > dstH)
        return;
        
    int x, y;
    int line = 0, offset = 0;
	offset = pos.x() < 0 ? srcW + pos.x() : 0;
	line = pos.y() < 0 ? srcH + pos.y() : 0;
	srcW = pos.x() < 0 ? (srcW + pos.x()) : srcW;
	srcH = pos.y() < 0 ? (srcH + pos.y()) : srcH;
    int startX = (pos.x() < 0 ? 0 : pos.x());
    int startY = (pos.y() < 0 ? 0 : pos.y());
    int endX = (startX + srcW > dstW) ? dstW : (startX + srcW);
    int endY = (startY + srcH > dstH) ? dstH : (startY + srcH);
    
    for (y = startY; y < endY; ++y, ++line)
    {
        QRgb *src = (QRgb*)srcImage->scanLine(line) + offset;
        QRgb *dst = (QRgb*)mImage->scanLine(y) + startX;

        for (x = startX; x < endX; ++x)
        {
            QRgb srcPixel = *src++;
            
            if (qAlpha(srcPixel) == 255)
            {
                *dst = srcPixel;
            }
            
            dst++;
        }
    }
}
